/*
 * dac.c
 *
 * Created: 6/18/2013 11:24:53 PM
 *  Author: Tim
 */ 

#include <math.h>
#include "dac.h"
#include "../../board/board.h"
#include "../allocations.h"

dacTable_t	dac0Table;
dacTable_t	dac1Table;

void dacSetup()
{
	//Enable use of the DAC via un-Peripheral-"CLOCK"-Blocking it
	prEnable(DAC_PR);
	
	//To get the best ouptut claity possible from the DAC, we need to load the calibration values
	DACB.CH0GAINCAL = ReadCalibrationByte( offsetof(NVM_PROD_SIGNATURES_t, DACB0GAINCAL) );
	DACB.CH0OFFSETCAL = ReadCalibrationByte( offsetof(NVM_PROD_SIGNATURES_t, DACB0OFFCAL) );
	DACB.CH1GAINCAL = ReadCalibrationByte( offsetof(NVM_PROD_SIGNATURES_t, DACB1GAINCAL) );
	DACB.CH1OFFSETCAL = ReadCalibrationByte( offsetof(NVM_PROD_SIGNATURES_t, DACB1OFFCAL) );
	
//	PORTD.DIRSET = 0x0C;
	
	DACB.CTRLB = DAC_CHSEL_DUAL_gc;
	DACB.CTRLC = DAC_REFSEL_AREFB_gc;
	DACB.CTRLA = DAC_CH0EN_bm | DAC_CH1EN_bm;
	DACB.TIMCTRL = DAC_REFRESH_128CLK_gc;
	DACB.CTRLA |= DAC_ENABLE_bm;
	
	
}


status_t dacWaveformTableInit(uint8_t dacCh,dacWave_t wave, uint16_t freq, uint16_t amp, uint16_t offset)
{
	dacTable_t *table;
	
	//check to see if the waveform amplitude is too big
	if(amp > 2047)					return OPP_FAILED;
	//check to see if the waveform amplitude and offset together are too big
	else if (offset + amp > 4095)	return OPP_FAILED;
	else if (amp> offset)			return OPP_FAILED;
	
	
	//determine which dac channel was chosen 
	//once this is known the respective waveform table is selected
	if (dacCh)	table = &dac1Table;
	else		table = &dac0Table;	
	
	//init the table begin ptr
	table->tableBegin = (uint16_t *)table->waveData[0];
	//init the table data ptr
	table->dacData = (uint16_t *)table->waveData[0];
	
	//base on the wave chosen select the correct method for filling the waveform table
	switch(wave)
	{
		case SINE_WAVE:
			fillTable_sine((dacTable_t *)table,freq,amp,offset);
		break;
		case SQUARE_WAVE:
			fillTable_square((dacTable_t *)table,freq,amp,offset);
		break;
		case SAWTOOTH_WAVE:
			fillTable_sawtooth((dacTable_t *)table,freq,amp,offset);
		break;
		case TRIANGLE_WAVE:
			fillTable_triangle((dacTable_t *)table,freq,amp,offset);
		break;
		case NULL_WAVE:
			return OPP_FAILED;
		break;

	}
	
	if(dacCh)
	{
		daqAlloc.analogAlloc.analogOutCh1Alloc = FEATURE_UTILIZED;
	}
	else
	{
		daqAlloc.analogAlloc.analogOutCh0Alloc = FEATURE_UTILIZED;
	}
	return OPP_SUCCESSFUL;
}

void fillTable_sine(dacTable_t *table, uint16_t freq, uint16_t amp, uint16_t offset)
{
	//determine the depth or number of samples of the waveform at the given frequency
	table->tableDepth	= round(waveFormTableSize/(float)freq)-1;
	
	table->tableEnd = &table->waveData[table->tableDepth];
	
	//determine how many radians are between each step
	double	stepSize	=  2*M_PI/(table->tableDepth-1);
	//the -1 is there due to the following
	// example: for readings (denoted by |) there are 4 steps (denoted by -). Basically don\t forget we start at 0
	// |-|-|-|-|
	
	uint8_t i;
	
	for(i=0; i<= table->tableDepth;i++)
	{
		//This calculation works under the principle of the unit circle
		//the sin value can be multiplied by the amplitude to get the desired magnitude
		//and the offset can be summed to shift the center of the wave	
		table->waveData[i] = round(offset + amp*sin(stepSize*i));
	}
}

void fillTable_square(dacTable_t *table, uint16_t freq, uint16_t amp, uint16_t offset)
{
	//determine the depth or number of samples of the waveform at the given frequency
	table->tableDepth	= round(waveFormTableSize/(float)freq)-1;
	
	table->tableEnd = &table->waveData[table->tableDepth];
	
	uint8_t i;
	
	//for the first half of the wave make the signal "high"
	//half is ~ since /2 is an integer opperation.
	//if there is an odd number of positions, then the signal cannot truly have a 50% duty cycle
	for(i=0; i< table->tableDepth/2;i++)
	{
		//signal is centered at the "offset" value.
		//the swing of the signal is determined by the "amp"
		table->waveData[i] = round(offset + amp);
	}
	for(;i<=table->tableDepth;i++)
	{
		//signal is centered at the "offset" value.
		//the swing of the signal is determined by the "amp"
		table->waveData[i] = round(offset - amp);
	}
}

void fillTable_sawtooth(dacTable_t *table, uint16_t freq, uint16_t amp, uint16_t offset)
{
	
	/********************************SAWTOOTH********************************/
	/*| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /| /|*/
	/*|/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |/ |*/
	/************************************************************************/
	
	
	//determine the depth or number of samples of the waveform at the given frequency
	table->tableDepth	= round(waveFormTableSize/(float)freq)-1;
	
	
	table->tableEnd = &table->waveData[table->tableDepth];
	
	
	//For a sawtooth wave (drawn above) the peak-to-peak voltage (amp*2) is divided by
	//the table depth, in order to determine the step size of the waveform.
	//contrary to the sin wave param of the same name, this value is of the same unit as amplitude.
	//so the strategy is to start at the lowest point of the wave and incrementally add steps to
	//reach the highest point, or end of the wave.
	double stepSize = (2*amp)/(float)(table->tableDepth-1);
	
	
	uint8_t i;
	
	for(i=0; i<= table->tableDepth;i++)
	{
		//the wave starts at the lowest value.  It is then increased by the step size until
		//the table of specified "tableDepth" is filled 
		table->waveData[i] = round(offset - amp + stepSize*i);
	}
}

void fillTable_triangle(dacTable_t *table, uint16_t freq, uint16_t amp, uint16_t offset)
{
	/********************************TRIANGLE********************************/
	/* /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /\  /*/                                                                   
	/*/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/  \/ */                                                                     
	/************************************************************************/
	
	//determine the depth or number of samples of the waveform at the given frequency
	table->tableDepth	= round(waveFormTableSize/(float)freq)-1;
	
	table->tableEnd = &table->waveData[table->tableDepth];
	
	
	//A triangle wave is essentially a two sided sawtooth, because there is an up and down slope
	//for the step size we use twice the slope of a sawtooth since the DAC output travels twice the distance
	//for the same frequency.  
	double stepSize = (4*amp)/(table->tableDepth-1);
	
	//this value is an integer offset of the variable icrement variable i
	uint8_t iOffset = 0;
	
	uint8_t i;
	
	//since 1) triangle wave is symmetric, and 2 table depth could divide unevenly
	//we use the round function to make this loop uses half of the table or half + 1 slot.
	for(i=0; i < round(table->tableDepth/2.0);i++)
	{
		//like the sawtooth wave we start at the lowest part of the wave and add
		//linearly increasing amounts to get the signal to its maximum.
		//In this case that point just so happens to be in the middle of the
		//signal period
		table->waveData[i] = round(offset - amp + stepSize*i);
	}
	
	for(iOffset = 1; i< table->tableDepth;i++)
	{
		//on the downward slope we simply reuse the symmetric values
		//This is done by incrementing the iOffset and multiplying it by 2 
		table->waveData[i] = table->waveData[i-iOffset++*2];
	}
	
}

void dacUpdate(uint8_t dacCh, uint16_t data)
{
	if(dacCh & DAC_CH1_bp && daqAlloc.analogAlloc.analogOutCh1Alloc == FEATURE_FREE)	DACB.CH1DATA = data;
	if(dacCh & DAC_CH0_bp && daqAlloc.analogAlloc.analogOutCh0Alloc == FEATURE_FREE)	DACB.CH0DATA = data;
}

uint16_t dacSampleTable(uint8_t dacCh)
{
	uint16_t data;
	if(dacCh)
	{
		data = *dac1Table.dacData++;
		if(dac1Table.dacData == dac1Table.tableEnd)  
		{
			dac1Table.dacData = (uint16_t *)dac1Table.tableBegin;
		}
	}
	else
	{
		data = *dac0Table.dacData++;
		if(dac0Table.dacData == dac0Table.tableEnd) 
		{
			dac0Table.dacData = (uint16_t *)dac0Table.tableBegin;
		}
	}
	return data;
}